﻿/**
 *       Project: ServerLib
 *     Copyright: Copyright (C) 2008, Rob Kennedy
 *   Create Date: 2008-07-14 10:16 PM
 * Modified Date: 2008-07-14 10:16 PM    RK  Added comments
 *      Comments: Ignore SA1633.
 *      
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace ServerLib.Network
{
    using System;
    using System.Text;
    using System.IO;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Threading;
    using System.Security.Authentication;
    using System.Security.Cryptography.X509Certificates;

    /// --------------------------------------------------------------------
    /// Project	 : ServerLib
    /// Class	 : EnhancedSocket
    /// Inherits : None.
    /// --------------------------------------------------------------------
    /// <summary>
    /// A combination of TcpClient, TcpListener and Socket, this class adds 
    /// several built in capabilites not found in any socket classes. Also is 
    /// capable of secure socket communications. Data is received and 
    /// reported back through the DataLineReceived event.
    /// </summary>
    /// <authorname>Rob Kennedy</authorname>
    /// <remarks></remarks>
    /// --------------------------------------------------------------------
    public class EnhancedSocket : IDisposable
    {
        #region Private Constants

        /// <summary>Contains the end of line tag key to denote when a line has been received</summary>
        private const string EndOfLineTag = "\r\n";

        #endregion

        #region Private Properties

        /// <summary>Contains the internal socket object</summary>
        private Socket internalSocket;

        /// <summary>Contains the local end point address</summary>
        private IPEndPoint localEndPoint;

        /// <summary>Contains the internal socket address family</summary>
        private AddressFamily addressFamily = AddressFamily.InterNetwork;

        /// <summary>Contains the socket active status</summary>
        private bool socketActive;

        /// <summary>Contains the socket security type</summary>
        private SocketSecurityType securityType = SocketSecurityType.None;
        
        /// <summary>Contains the secure socket stream object</summary>
        private SslStream sslStream;
        
        /// <summary>Contains the unsecure socket stream object</summary>
        private NetworkStream dataStream;

        /// <summary>Contains the certificate file path to use for secure sockets</summary>
        private string certificateFilePath;

        /// <summary>Contains the certificate subject name to usef for secure sockets</summary>
        private string certificateStoreSubjectName;

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initialize a new instance of the Enhanced Socket class
        /// </summary>
        public EnhancedSocket() : this(AddressFamily.InterNetwork)
        {
        }
        
        /// <summary>
        /// Initialize a new instance of the Enhanced Socket class
        /// </summary>
        /// <param name="addressFamily"><see cref="AddressFamily"/> value</param>
        /// <exception cref="ArgumentException"><paramref name="addressFamily"/> is an unuseable family.</exception>
        /// <exception cref="SocketException"><paramref name="addressFamily"/> results in an invalid socket.</exception>
        public EnhancedSocket(AddressFamily addressFamily)
        {
            addressFamily = AddressFamily.InterNetwork;
            if (addressFamily != AddressFamily.InterNetwork && addressFamily != AddressFamily.InterNetworkV6)
            {
                throw new ArgumentException("Invalid address family");
            }

            this.addressFamily = addressFamily;
            this.Initialize();
        }

        /// <summary>
        /// Initialize a new instance of the Enhanced Socket class
        /// </summary>
        /// <param name="predefinedSocket">A predefined <see cref="Socket"/> class</param>
        /// <exception cref="NullReferenceException"><paramref name="predefinedSocket"/> is an undefined reference.</exception>
        public EnhancedSocket(Socket predefinedSocket)
        {
            this.internalSocket = predefinedSocket;
            this.socketActive = false;
            if (predefinedSocket == null)
            {
                throw new NullReferenceException("Predefined socket is null");
            }
        }

        /// <summary>
        /// Initialize a new instance of the Enhanced Socket class
        /// </summary>
        /// <param name="localEP">local IP End Point address</param>
        /// <exception cref="NullReferenceException"><paramref name="localEP"/> is an undefined reference.</exception>
        /// <exception cref="SocketException"><paramref name="localEP"/> results in an invalid socket.</exception>
        public EnhancedSocket(IPEndPoint localEP)
        {
            if (localEP == null)
            {
                throw new ArgumentNullException("localEP");
            }

            this.localEndPoint = localEP;
            this.addressFamily = localEP.AddressFamily;
            this.Initialize();
            this.internalSocket.Bind(localEP);
        }

        #endregion

        #region Public Enumerations

        /// <summary>
        ///  Contains an enumerated list of socket security modes
        /// </summary>
        public enum SocketSecurityType
        {
            /// <summary>No security mode, data is sent as clear text</summary>
            None = 0,

            /// <summary>Using SSL security mode, encrypted data using SSL</summary>
            SSL = 1,

            /// <summary>Using TLS security mode, encrypted data using TLSv1</summary>
            TLSv1 = 2
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the internal Socket object
        /// </summary>
        public Socket InternalSocket
        {
            get 
            {
                return this.internalSocket; 
            }
            
            set 
            { 
                this.internalSocket = value; 
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the socket is active 
        /// </summary>
        public bool Active
        {
            get 
            {
                return this.socketActive; 
            }

            set 
            { 
                this.socketActive = value; 
            }
        }

        /// <summary>
        /// Gets the number of bytes available to read on the Socket
        /// </summary>
        public int Available
        {
            get
            {
                int resultValue = 0;

                if (this.internalSocket != null ? this.internalSocket.Connected : false)
                {
                    resultValue = this.internalSocket.Available;
                }

                return resultValue;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the Socket is connected
        /// </summary>
        public bool Connected
        {
            get 
            {
                bool resultValue = false;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.Connected;
                }
                
                return resultValue;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Socket Exclusive address option is enabled
        /// </summary>
        public bool ExclusiveAddressUse
        {
            get
            {
                bool resultValue = false;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.ExclusiveAddressUse;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.ExclusiveAddressUse = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the Linger state of the Socket
        /// </summary>
        public LingerOption LingerState
        {
            get
            {
                LingerOption resultValue = null;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.LingerState;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.LingerState = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Socket is using
        /// the Nagle algorithm.
        /// </summary>
        public bool NoDelay
        {
            get
            {
                bool resultValue = false;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.NoDelay;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.NoDelay = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the Socket data receive buffer size
        /// </summary>
        public int ReceiveBufferSize
        {
            get
            {
                int resultValue = 0;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.ReceiveBufferSize;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.ReceiveBufferSize = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the socket data receive timeout in milliseconds
        /// </summary>
        public int ReceiveTimeout
        {
            get
            {
                int resultValue = 0;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.ReceiveTimeout;
                }
                
                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.ReceiveTimeout = value;
                }
            }
        }

        /// <summary>Gets or sets the Socket data send buffer size</summary>
        public int SendBufferSize
        {
            get
            {
                int resultValue = 0;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.SendBufferSize;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.SendBufferSize = value;
                }
            }
        }

        /// <summary>Gets or sets the Socket send timeout in milliseconds</summary>
        public int SendTimeout
        {
            get
            {
                int resultValue = 0;

                if (this.internalSocket != null)
                {
                    resultValue = this.internalSocket.SendTimeout;
                }

                return resultValue;
            }

            set
            {
                if (this.internalSocket != null)
                {
                    this.internalSocket.SendTimeout = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the Secure socket certificate file Path
        /// </summary>
        public string CertificateFilePath
        {
            get 
            { 
                return this.certificateFilePath; 
            }
            
            set 
            { 
                this.certificateFilePath = value; 
            }
        }

        /// <summary>
        /// Gets or sets the Secure socket certificate subject name used to search
        /// the global certificate store for a certificate with the matching subject
        /// name.
        /// </summary>
        public string CertificateStoreSubjectName
        {
            get 
            { 
                return this.certificateStoreSubjectName; 
            }

            set 
            { 
                this.certificateStoreSubjectName = value; 
            }
        }

        /// <summary>
        /// Gets or sets the Socket security type
        /// </summary>
        public SocketSecurityType SecurityType
        {
            get 
            { 
                return this.securityType; 
            }

            set 
            {
                this.securityType = value;
            }
        }

        /// <summary>
        /// Gets the locl host name of the Socket
        /// </summary>
        public string LocalHostName
        {
            get
            {
                string resultValue = string.Empty;

                try
                {
                    resultValue = System.Net.Dns.GetHostName();
                }
                catch (SocketException ex)
                {
                    // TODO: Don't really care, but may want to log error
                }

                // use IP address if no name found
                if (resultValue.Length == 0)
                {
                    resultValue = this.LocalIP;
                }

                return resultValue;
            }
        }

        /// <summary>
        /// Gets the local IP address string of the Socket
        /// </summary>
        public string LocalIP
        {
            get
            {
                string resultValue = string.Empty;
                
                try
                {
                    if (this.internalSocket != null)
                    {
                        resultValue = IPAddress.Parse(((IPEndPoint)this.internalSocket.LocalEndPoint).Address.ToString()).ToString();
                    }
                }
                catch (SocketException ex)
                {
                    // TODO: Don't really care, but may want to log error
                }

                return resultValue;
            }
        }

        /// <summary>
        /// Gets the remote host name of the Socket
        /// </summary>
        public string RemoteHostName
        {
            get
            {
                string resultValue = string.Empty;
                try
                {
                    if (this.internalSocket != null)
                    {
                        resultValue = System.Net.Dns.GetHostEntry(((IPEndPoint)this.internalSocket.RemoteEndPoint).Address).HostName;
                    }
                }
                catch (SocketException sex)
                {
                    if (sex.ErrorCode == 11001)
                    {
                        resultValue = this.RemoteIP;
                    }
                    else
                    {
                        throw sex;
                    }
                }

                return resultValue;
            }
        }

        /// <summary>
        /// Gets the remote ip address string of the Socket
        /// </summary>
        public string RemoteIP
        {
            get
            {
                string resultValue = string.Empty;
                try
                {
                    if (this.internalSocket != null)
                    {
                        resultValue = IPAddress.Parse(((IPEndPoint)this.internalSocket.RemoteEndPoint).Address.ToString()).ToString();
                    }
                }
                catch (SocketException ex)
                { 
                }

                return resultValue;
            }
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Accepts an incoming connection request and returns an
        /// EnahancedSocket object for the new client connection.
        /// </summary>
        /// <returns>Returns a new EnhancedSocket object for the new remote connection</returns>
        /// <exception cref="InvalidOperationException">Socket is not active error</exception>
        public EnhancedSocket AcceptEnahancedSocket()
        {
            if (!this.socketActive)
            {
                throw new InvalidOperationException("network stopped");
            }

            return new EnhancedSocket(this.internalSocket.Accept());
        }

        /// <summary>
        /// Accepts an incoming connection request and returns a
        /// Socket object for the new client connection.
        /// </summary>
        /// <returns>Returns a Socket object for the new remote connection</returns>
        /// <exception cref="InvalidOperationException">Socket is not active error</exception>
        public Socket AcceptSocket()
        {
            if (!this.socketActive)
            {
                throw new InvalidOperationException("network stopped");
            }

            return this.internalSocket.Accept();
        }

        /// <summary>
        /// Determine if data is pending to be read from the Socket
        /// </summary>
        /// <returns>Returns true if received data exists in the Socket read buffer</returns>
        /// <exception cref="InvalidOperationException">Socket is not active error</exception>
        public bool Pending()
        {
            if (!this.socketActive)
            {
                throw new InvalidOperationException("network stopped");
            }

            return this.internalSocket.Poll(0, SelectMode.SelectRead);
        }

        /// <summary>
        /// Starts the socket listening state with no backlog requirement
        /// </summary>
        public void Start() 
        {
            this.Start(0x7fffffff); 
        }

        /// <summary>
        /// Starts the Socket listening state with a specific number of backlog requirements
        /// </summary>
        /// <param name="backlog">The maximum number of pending connections queue</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="backlog"/> results in out of range error</exception>
        /// <exception cref="InvalidOperationException">Socket is not defined error</exception>
        public void Start(int backlog)
        {
            if (backlog > 0x7fffffff || backlog < 0)
            {
                throw new ArgumentOutOfRangeException("backlog");
            }

            if (this.internalSocket == null)
            {
                throw new InvalidOperationException("socket undefined");
            }
            
            if (!this.socketActive)
            {
                this.internalSocket.Listen(backlog);
                this.socketActive = true;
            }
        }

        /// <summary>
        /// Stops the Socket listening state and reinitializes the socket.
        /// </summary>
        public void Stop()
        {
            if (this.internalSocket != null)
            {
                this.Dispose();
            }

            this.Initialize();

            this.internalSocket.ExclusiveAddressUse = true;
        }

        /// <summary>
        /// Closes the Socket connection and disposes the socket.
        /// </summary>
        public void Close()
        {
            this.Dispose();
        }

        /// <summary>
        /// Connect the Socket to a remote end point address
        /// </summary>
        /// <param name="remoteEP">The remote end point address to connect to</param>
        /// <exception cref="ArgumentNullException"><paramref name="remoteEP"/> results in a null exception error</exception>
        public void Connect(IPEndPoint remoteEP)
        {
            if (remoteEP == null)
            {
                throw new ArgumentNullException("remoteEP");
            }

            if (this.internalSocket != null)
            {
                this.internalSocket.Connect(remoteEP);
                this.socketActive = true;
            }
        }

        /// <summary>
        /// Connect the Socket to a specified IPAddress and port
        /// </summary>
        /// <param name="address">The remote ip Address to connect to</param>
        /// <param name="port">The port to connect to</param>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> results in a null exception error</exception>
        public void Connect(IPAddress address, int port)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            this.Connect(new IPEndPoint(address, port));
        }

        /// <summary>
        /// Connect the Socket to a specified host Name and port
        /// </summary>
        /// <param name="hostName">The host name to connect to</param>
        /// <param name="port">The port to connect to</param>
        /// <exception cref="ArgumentNullException"><paramref name="hostName"/> results in a null exception error</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> results in out of range error</exception>
        /// <exception cref="SocketException">Socket is already connected error</exception>
        public void Connect(string hostName, int port)
        {
            if (hostName == null)
            {
                throw new ArgumentNullException("hostname");
            }
            
            if (port < 1)
            {
                throw new ArgumentOutOfRangeException("port");
            }
            
            if (this.internalSocket != null ? this.internalSocket.Connected : false)
            {
                throw new SocketException((int)SocketError.IsConnected);
            }

            IPAddress[] hostAddresses = System.Net.Dns.GetHostAddresses(hostName);

            if (hostAddresses != null)
            {
                if (this.internalSocket == null)
                {
                    if (Socket.SupportsIPv4)
                    {
                        this.addressFamily = AddressFamily.InterNetwork;
                    }

                    if (Socket.OSSupportsIPv6)
                    {
                        this.addressFamily = AddressFamily.InterNetworkV6;
                    }

                    this.internalSocket = new Socket(this.addressFamily, SocketType.Stream, ProtocolType.Tcp);
                }

                foreach (IPAddress address in hostAddresses)
                {
                    try
                    {
                        this.internalSocket.Connect(address, port);

                        if (this.internalSocket.Connected)
                        {
                            this.socketActive = true;
                            break;
                        }
                    }
                    catch (ThreadAbortException tae)
                    {
                        throw tae;
                    }
                    catch (StackOverflowException soe)
                    {
                        throw soe;
                    }
                    catch (OutOfMemoryException oome)
                    {
                        throw oome;
                    }
                    catch (Exception ex)
                    {
                        throw new SocketException((int)SocketError.NotConnected);
                    }
                }
            }
        }

        /// <summary>
        /// Close and Dispose of any open Socket connection objects
        /// </summary>
        public void Dispose()
        {
            if (this.internalSocket != null)
            {
                if (this.sslStream != null)
                {
                    this.sslStream.Close();
                    this.sslStream.Dispose();
                    this.sslStream = null;
                }

                if (this.dataStream != null)
                {
                    this.dataStream.Close();
                    this.dataStream.Dispose();
                    this.dataStream = null;
                }

                try
                {
                    this.internalSocket.Shutdown(SocketShutdown.Both);
                }
                catch (SocketException sex)
                {
                    // TODO: Log an error if we care
                }
                finally
                {
                    this.internalSocket.Close();
                }

                this.socketActive = false;
            }
        }

        /// <summary>
        /// Returns a NetworkStream object instance of the current Socket
        /// </summary>
        /// <returns>Returns a valid NetworkStream object for the underlying Socket</returns>
        public NetworkStream GetStream()
        {
            if (this.internalSocket != null ? this.internalSocket.Connected : false)
            {
                if (this.dataStream == null)
                {
                    this.dataStream = new NetworkStream(this.internalSocket, true);
                }
            }
            else
            {
                throw new InvalidOperationException("Not connected");
            }

            return this.dataStream;
        }

        /// <summary>
        /// Returns a Secure Stream object instance of the current socket. If a valid certificate
        /// is found the Secure Stream object will authenticate using the specified mode and 
        /// will either throw an exeception or return a secure socket layer stream object.
        /// </summary>
        /// <param name="authenticateAsServer">If true authenticates as the server, otherwise as client</param>
        /// <returns>Returns an authenticated SslStream object for the underlying Socket</returns>
        /// <exception cref="SocketException">Socket is not connected error</exception>
        public SslStream GetSecureStream(bool authenticateAsServer)
        {
            if (this.internalSocket != null ? this.internalSocket.Connected : false)
            {
                if (this.sslStream == null)
                {
                    this.sslStream = new SslStream(this.GetStream(), true);

                    if (this.sslStream != null)
                    {
                        try
                        {
                            // attempt to get a server certificate using the subject name specified.
                            X509Certificate cert = this.GetServerCert(X509FindType.FindBySubjectDistinguishedName, this.certificateStoreSubjectName);

                            // if no certificate and a certificate file was specified, try to create
                            // a new certificate from the file.
                            if (cert == null ? File.Exists(this.certificateFilePath) : false)
                            {
                                cert = new X509Certificate(this.certificateFilePath);
                            }

                            // if a cert object exists, authenticate using the specified method
                            if (cert != null)
                            {
                                if (authenticateAsServer)
                                {
                                    this.sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls, false);
                                }
                                else
                                {
                                    this.sslStream.AuthenticateAsClient(this.RemoteHostName);
                                }
                            }
                        }
                        catch (IOException iox)
                        {
                            // shut down since there was probably an invalid negotiation packet
                            this.Close();
                            throw iox;
                        }
                        catch (AuthenticationException aex)
                        {
                            this.Close();
                            throw aex;
                        }
                        catch (Exception ex)
                        {
                            this.Close();
                            throw ex;
                        }
                    }
                }
            }
            else
            {
                throw new SocketException((int)SocketError.NotConnected);
            }

            return this.sslStream;
        }

        /// <summary>
        /// A bocking entry point which will read all data in the receive buffer
        /// and will fire DataLineReceived events when an end of line tag has 
        /// been found.
        /// </summary>
        /// <param name="maximumBufferLength">The maximum number of bytes to read</param>
        /// <exception cref="ArgumentNullException">Security type does not match socket setting error</exception>
        public void ReadLines(int maximumBufferLength)
        {
            byte[] receiveBytes = new byte[maximumBufferLength];
            string strReceiveBuffer = string.Empty;
            string strLineBuffer = string.Empty;
            int returnCount = 0;

            // if we are in a secure mode...
            if (this.securityType != SocketSecurityType.None)
            {
                // ...and we have no secure stream...
                if (this.sslStream == null)
                {
                    // get a new secure stream 
                    // TODO: get a server/client state; for now assume server
                    this.GetSecureStream(true);
                }

                // if we still dont have a secure stream, throw exception
                if (this.sslStream == null)
                {
                    throw new ArgumentNullException("poSslStream");
                }
            }

            DateTime lastRead = DateTime.Now;
            int throttleCount = 0;
            
            // loop while we are still active and connected
            while (this.socketActive && this.Connected && returnCount > -1)
            {
                try
                {
                    // if plain text, use standard socket receive
                    if (this.securityType == SocketSecurityType.None)
                    {
                        int readAvail = this.internalSocket.Available;
                        returnCount = this.internalSocket.Receive(receiveBytes, readAvail, SocketFlags.None);
                    }
                    else if (this.sslStream.IsEncrypted)
                    {
                        // otherwise read secure socket stream
                        returnCount = this.sslStream.Read(receiveBytes, 0, receiveBytes.Length);
                    }

                    // if we have bytes get the string
                    if (returnCount > 0)
                    {
                        strReceiveBuffer = System.Text.UTF8Encoding.UTF8.GetString(receiveBytes, 0, returnCount);
                    }
                    else
                    {
                        strReceiveBuffer = string.Empty;
                    }
                }
                catch (SocketException ex)
                {
                    // TODO: Log any potential error here   
                }
                
                // if we have data received then...
                if (returnCount > 0)
                {
                    // decriment our throttle counter if needed
                    if (throttleCount > 0)
                    {
                        throttleCount--;
                    }

                    // get the next end of line tag if any
                    int eolIndex = strReceiveBuffer.IndexOf(EndOfLineTag);

                    // if we have an end of line, parse it out
                    if (eolIndex > -1)
                    {
                        // keep parsing while we still have end of line tags
                        while (eolIndex > -1)
                        {
                            if (strLineBuffer.Length > 0)
                            {
                                strLineBuffer += strReceiveBuffer.Substring(0, eolIndex);
                            }
                            else
                            {
                                strLineBuffer = strReceiveBuffer.Substring(0, eolIndex);
                            }

                            // fire the data line received event
                            this.OnDataLineReceive(strLineBuffer);

                            strLineBuffer = string.Empty;

                            if (strReceiveBuffer.Length > eolIndex + EndOfLineTag.Length)
                            {
                                strReceiveBuffer = strReceiveBuffer.Substring(eolIndex + EndOfLineTag.Length);
                            }
                            else
                            {
                                strReceiveBuffer = string.Empty;
                            }

                            eolIndex = strReceiveBuffer.IndexOf(EndOfLineTag);
                        }
                    }
                    else
                    {
                        strLineBuffer += strReceiveBuffer;
                    }
                }
                else
                {
                    // If a client disconnects remotely the local server socket can still remain open.
                    // when this occurs the Receive function will return immediately without error and will
                    // report 0 bytes received. This will continue over and over resulting in a CPU race condition.
                    // To avoid this race, the throttle count is used to keep check on this condition. If more than
                    // 5 loops occur with no bytes returned in a row, the socket will close. This is the only solution
                    // I have for this issue right now.
                    throttleCount++;
                    
                    if (throttleCount > 5)
                    {
                        this.Close();
                    }

                    lastRead = DateTime.Now;
                }
            }
        }

        /// <summary>
        /// Write a string the the Socket send buffer
        /// </summary>
        /// <param name="outputBuffer">The string to write to the Socket</param>
        public void Write(string outputBuffer)
        {
            try
            {
                if (this.Connected)
                {
                    // if not using secure mode, use Socket send
                    if (this.securityType == SocketSecurityType.None)
                    {
                        this.internalSocket.Send(System.Text.UTF8Encoding.UTF8.GetBytes(outputBuffer));
                    }
                    else
                    {
                        // if using secure mode, use secure stream write

                        // if no secure stream specified, get one
                        if (this.sslStream == null)
                        {
                            // TODO: for now assume server, get state in future
                            this.GetSecureStream(true);
                        }

                        if (this.sslStream != null ? this.sslStream.IsEncrypted : false)
                        {
                            this.sslStream.Write(System.Text.UTF8Encoding.UTF8.GetBytes(outputBuffer));
                        }
                    }
                }
            }
            catch (SocketException sex)
            {
                throw sex;
            }
        }

        /// <summary>
        /// Write an entire file to the Socket send buffer
        /// </summary>
        /// <param name="fileName">The file name path to send</param>
        /// <param name="base64Encode">True if base64 encoding the file data before sending</param>
        public void WriteFile(string fileName, bool base64Encode)
        {
            FileInfo fi = new FileInfo(fileName);

            if (fi.Exists)
            {
                string fileBuffer = string.Empty;
                FileStream fs = fi.OpenRead();
                StreamReader sr = new StreamReader(fs);
                fileBuffer = sr.ReadToEnd();
                sr.Close();

                if (base64Encode)
                {
                    this.Write(Convert.ToBase64String(System.Text.UTF8Encoding.UTF8.GetBytes(fileBuffer)));
                }
                else
                {
                    this.Write(fileBuffer);
                }
            }
        }

        #endregion

        #region Public Deligates

        /// <summary>
        /// Public delegate for the Data Line Receive event handler
        /// </summary>
        /// <param name="o">Event object</param>
        /// <param name="e">Event Arguments boejct</param>
        public delegate void DataLineReceiveHandler(object o, DataLineReceiveArgs e);
       
        /// <summary>
        /// Public event for the DataLineReceieved event
        /// </summary>
        public event DataLineReceiveHandler DataLineReceived;

        #endregion

        #region Private Methods

        /// <summary>
        /// Used to initialize the socket object and active status
        /// </summary>
        private void Initialize()
        {
            this.internalSocket = new Socket(this.addressFamily, SocketType.Stream, ProtocolType.Tcp);
            this.socketActive = false;
        }

        /// <summary>
        /// Fires the DataLineReceived event
        /// </summary>
        /// <param name="dataLine">The data line received from the Socket</param>
        private void OnDataLineReceive(string dataLine)
        {
            if (this.DataLineReceived != null)
            {
                this.DataLineReceived(new object(), new DataLineReceiveArgs(dataLine));
            }
        }

        /// <summary>
        /// Gets the locally stored Server certificate
        /// </summary>
        /// <param name="findBy">The X509 find type</param>
        /// <param name="key">The key to search for</param>
        /// <returns>Returns an X509Certificate object found</returns>
        private X509Certificate GetServerCert(X509FindType findBy, string key)
        {
            X509Certificate resultValue = null;
            X509Store store = new X509Store(StoreLocation.LocalMachine);

            store.Open(OpenFlags.ReadOnly);
            
            X509CertificateCollection cert = store.Certificates.Find(findBy, key, false);
            
            if (cert != null ? cert.Count > 0 : false)
            {
                resultValue = cert[0];
            }

            return resultValue;
        }

        #endregion
    }
}
